#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

// Valor máximo a ser gerado aleatoriamente
#define MAX 1000

// Struct para armazenar as variáveis das threads, possibilitando acessá-las na main
typedef struct thread_arguments{
    int sockfd;
    int newsockfd;
    int port_number;
    int *vetor;
    int tamanho_vetor;
}thread_arg;

/**
 * Função que fará o merge ordenado dos dois vetores recebidos.
 * Baseado em http://www.ime.usp.br/~pf/mac0122-2002/aulas/mergesort.html
 */
void mergeAB(int vetorSaida[], int vetorA[], int tamA, int vetorB[], int tamB)
{
    int i = 0, j = 0, k;
    for (k = 0; k < tamA+tamB; k++) {
        if (i == tamA) {
           vetorSaida[k] = vetorB[j++];
           continue;
        }
        if (j == tamB) {
           vetorSaida[k] = vetorA[i++];
           continue;
        }
        if (vetorA[i] < vetorB[j])
            vetorSaida[k] = vetorA[i++];
        else vetorSaida[k] = vetorB[j++];
    }
}

void trocar (int vetor[], int x, int y)
{

		int aux = vetor[x];
		vetor[x] = vetor[y];
		vetor[y] = aux;
}

/**
 * Função que ordenará o vetor passado como parâmetro
 * Baseado em http://www.cos.ufrj.br/~rfarias/cos121/aula_05.html
 */
void bubbleSort( int vetor[], int tam)
{

    int i, j;

    for ( i=tam-1; i > 0; i-- ){
        for ( j=0; j < i; j++ ){

            if ( vetor[j] > vetor[j+1])
                trocar(vetor, j, j+1);
        }
    }
}

// Função para mensagem de erro e término do programa
void error(const char *msg)
{
    perror(msg);
    exit(1);
}

/**
 * Função que será executada pelas threads, que executa os seguintes passos:
 * - estabelece conexão com um cliente
 * - aguarda o recebimento do tamanho do vetor que será enviado pelo cliente e confirma
 *   o recebimento
 * - aloca espaço para o vetor a ser recebido
 * - aguarda o envio do vetor e confirma o recebimento
 */
void *recebeVet(void *args){
	socklen_t clilen;
	char buffer[256];
	char confirm[20];
	struct sockaddr_in serv_addr, cli_addr;
	thread_arg *thr = (thread_arg *)args;
	 int n, i, tam;
	int v[1000];

	strcpy(confirm,"I got your message");

	thr->sockfd = socket(AF_INET, SOCK_STREAM, 0);

	if (thr->sockfd < 0)
		error("ERROR opening socket");

	bzero((char *) &serv_addr, sizeof(serv_addr));

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = INADDR_ANY;
	serv_addr.sin_port = htons(thr->port_number);
	if (bind(thr->sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
	      error("ERROR on binding");

		  
	listen(thr->sockfd, 5);
	clilen = sizeof(cli_addr);
	thr->newsockfd = accept(thr->sockfd, (struct sockaddr *) &cli_addr, &clilen);
	if (thr->newsockfd < 0)
	  error("ERROR on accept");

	n = read(thr->newsockfd, &tam, sizeof(int));
	if (n < 0) error("ERROR reading from socket");

	thr->tamanho_vetor = tam;

	n = write(thr->newsockfd, confirm, strlen(confirm));
	if (n < 0) error("ERROR writing to socket");

	thr->vetor = malloc(sizeof(int) * (thr->tamanho_vetor));

	n = read(thr->newsockfd, thr->vetor, sizeof(int)*(thr->tamanho_vetor));
	if (n < 0) error("ERROR reading from socket");

	n = write(thr->newsockfd, confirm, strlen(confirm));
	if (n < 0) error("ERROR writing to socket");
	

}

int main(int argc, char *argv[])
{
	int resThread;
	char buffer[256];
	char confirm[20];
	int  i;
	int vetor[1000], tam, tamPronto;
	int *vetorPronto;

	// Variáveis para conexão com o servidor no nível acima
	int sockfdServidor, portaServidor, resposta;
	struct sockaddr_in enderecoServidor;
	struct hostent *serverName;
	
	// Variáveis para a contagem do tempo.
	struct timeval t0,t1,t2,t3;
	int tempo;

	pthread_t threadRecebe1, threadRecebe2;
	thread_arg targ1, targ2;
	strcpy(confirm,"I got your message");

	if (argc != 5)
		 error ("Numero invalido de argumentos");

	// Recebe os parâmetros para as threads
	targ1.port_number = atoi(argv[3]); //porta nivel inferior 1
	targ2.port_number = atoi(argv[4]); //porta nivel inferior 2

	// Inicia as threads que receberão os vetores dos nós do nível inferior
	gettimeofday(&t0, 0);
	resThread = pthread_create(&threadRecebe1, NULL, recebeVet, (void *)&targ1);
	resThread = pthread_create(&threadRecebe2, NULL, recebeVet, (void *)&targ2);
	pthread_join(threadRecebe1, NULL);
	pthread_join(threadRecebe2, NULL);
	gettimeofday(&t1, 0);
    tempo = t1.tv_usec - t0.tv_usec;
    printf("\nTempo de Comunicacao com os Clientes: %d \n",tempo);


    // Ordena os dois vetores recebidos
	gettimeofday(&t0, 0);
    bubbleSort(targ1.vetor, targ1.tamanho_vetor);
    bubbleSort(targ2.vetor, targ2.tamanho_vetor);
	gettimeofday(&t1, 0);
    tempo = t1.tv_usec - t0.tv_usec;
    printf("\nTempo de  Ordenacao: %d \n",tempo);

    // Merge os dois vetores
    mergeAB(vetor, targ1.vetor, targ1.tamanho_vetor, targ2.vetor, targ2.tamanho_vetor);
    tam = targ1.tamanho_vetor + targ2.tamanho_vetor;

    // Abre conexão com o servidor no nível acima
	gettimeofday(&t0, 0);
	portaServidor = atoi(argv[2]);
	sockfdServidor = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfdServidor < 0)
		error("ERRO ao abrir socket");
	serverName = gethostbyname(argv[1]);
	if (serverName == NULL)
		error("ERRO host não encontrado");

     // Abre conexão com o servidor no nível acima
	bzero((char *) &enderecoServidor, sizeof(enderecoServidor));
	enderecoServidor.sin_family = AF_INET;
	bcopy((char *)serverName->h_addr, (char *)&enderecoServidor.sin_addr.s_addr, serverName->h_length);
	enderecoServidor.sin_port = htons(portaServidor);
	if (connect(sockfdServidor,(struct sockaddr *) &enderecoServidor,sizeof(enderecoServidor)) < 0)
		error("ERRO ao conectar");

    // Envia o tamanho do vetor
	resposta = write(sockfdServidor,&tam,sizeof(tam));
	if (resposta < 0)
		 error("ERRO ao escrever no socket");

    // Recebe a mensagem de confirmação
	bzero(&buffer,20);
	resposta = read(sockfdServidor, &buffer, 18);
    if(strcmp(buffer,confirm) == 0 && resposta > 0)//tamanho do vetor chegou ao servidor, enviar o vetor
		printf("Tamanho Chegou.\n");
   if (resposta < 0)
		error("ERRO ao ler do socket");

    // Envia o vetor para o servidor
	resposta = write(sockfdServidor, vetor, sizeof(int)*tam);//enviando vetor
	if (resposta < 0)
		error("ERRO ao escrever no socket");

    // Recebe a mensagem de confirmação
	bzero(&buffer,20);
	resposta = read(sockfdServidor,&buffer,18);
	if(strcmp(buffer,confirm) == 0 && resposta > 0)
		printf("Vetor chegou.\n");
	if (resposta < 0)
		error("ERRO ao ler do socket");
		
	gettimeofday(&t1, 0);
    tempo = t1.tv_usec - t0.tv_usec;
    printf("\nTempo de Comunicacao com o servidor: %d\n",tempo);
	
	// Recebe de Volta o Vetor Pronto.
	resposta = read(sockfdServidor, &tamPronto, sizeof(int));
	if (resposta < 0) error("ERROR reading from socket");
	
	printf("tamanho do vetor pronto: %d\n", tamPronto);
	
	vetorPronto = malloc(sizeof(int) * (tamPronto));
	resposta = recv(sockfdServidor,vetorPronto,sizeof(int)*tamPronto,MSG_WAITALL);	
	if (resposta < 0) error("ERROR reading from socket");

	resposta = write(sockfdServidor, confirm, strlen(confirm));
	if (resposta < 0) error("ERROR writing to socket");

	// Fim da recepcao do vetor pronto
			
	// *****************Envia vetor ordenado ao cliente.
	
	// Envia o tamanho para o cliente
	gettimeofday(&t0, 0);
	resposta = write(targ1.newsockfd, &tamPronto, sizeof(int));//enviando tam
	if (resposta < 0)
		error("ERRO ao escrever no socket");
	resposta = write(targ2.newsockfd, &tamPronto, sizeof(int));//enviando tam
	if (resposta < 0)
		error("ERRO ao escrever no socket");
	
	// Envia o vetor para o cliente
	resposta = write(targ1.newsockfd, vetorPronto, sizeof(int)*tamPronto);//enviando vetor
	if (resposta < 0)
		error("ERRO ao escrever no socket");
	resposta = write(targ2.newsockfd, vetorPronto, sizeof(int)*tamPronto);//enviando vetor
	if (resposta < 0)
		error("ERRO ao escrever no socket");

    // Recebe a mensagem de confirmação
	bzero(&buffer,20);
	resposta = read(targ1.newsockfd,&buffer,18);
	if(strcmp(buffer,confirm) == 0 && resposta > 0)
		printf("Vetor chegou.\n");
	if (resposta < 0)
		error("ERRO ao ler do socket");
	bzero(&buffer,20);
	resposta = read(targ2.newsockfd,&buffer,18);
	if(strcmp(buffer,confirm) == 0 && resposta > 0)
		printf("Vetor chegou.\n");
	if (resposta < 0)
		error("ERRO ao ler do socket");
	gettimeofday(&t1, 0);
    tempo = t1.tv_usec - t0.tv_usec;
    printf("\nTempo de Retorno do vetor aos clientes: %d\n",tempo);
	
	// *****************fim do envio do vetor ordenado ao cliente.
		

	close(sockfdServidor);
	close(portaServidor);
	return 0;
}

